Puya PY32F MCU support - #5106
Conversation
|
Hello @burgrp thank you for working on this. Have you seen this page? I think you will find it helpful. 😸 |
|
@burgrp I have switched the branch for this PR to Can you please rebase it against the latest Planning on ordering a few of these myself, thanks for working on it! |
|
@burgrp You are updating |
|
You are right... I will fix it and hopefully it will be possible to merge the PR. |
|
@b0ch3nski , the sub-module issue is fixed, all checks are passing |
|
Thanks! I think now there is some unrelated change also in |
|
@b0ch3nski looks that way to me as well: https://github.com/tinygo-org/tinygo/pull/5106/changes#diff-d7ea94295bc068e99e5edeacb50734849fb40d5aa39cbc68be5e55c0d90bd60a @burgrp one other thing that is needed is to add something to the smoketests to show that at least everything can build. Someplace appropriate in here, please: https://github.com/tinygo-org/tinygo/blob/dev/GNUmakefile#L567-L989 |
|
@b0ch3nski , @deadprogram smoketests added, net module update reverted |
| return uint8(p) & 0x0F | ||
| } | ||
|
|
||
| func (p Pin) getPort() (*py32.GPIO_Type, uint8) { |
There was a problem hiding this comment.
Better to use unsafe.Add() if you must. Probably even better to use a switch to avoid possible errors.
| switch config.Mode { | ||
|
|
||
| case PinInputFloating: | ||
| port.MODER.ReplaceBits(gpioModeInput, gpioModeMask, pos) |
There was a problem hiding this comment.
I have not installed/run the gen-device-py32 yet, but I assume there are probably some functions like SetMODERInput() that are being generated. Using these are usually preferable, to avoid possible bugs from setting unexpected bits.
There was a problem hiding this comment.
Unfortunately there is no SetMODERInput() generated. These helper functions are generated in form SetMODER_MODE0()...SetMODER_MODE15(), which are quite useless for parametric access.
I've fixed the machine constants to use the svd pin0 generated constants e.g.:
gpioModeOutput = py32.GPIO_MODER_MODE0_Output
so there is at least some binding to svd values.
| const PinInput PinMode = PinInputFloating | ||
|
|
||
| const ( | ||
| gpioModeInput = 0 |
There was a problem hiding this comment.
Are any of these being generated from the SVD file already? See comment below.
| // peripheral clock frequency. | ||
| func InitSerialWithClock(clockHz uint32) { | ||
| py32UARTClockHz = clockHz | ||
| //Serial.ConfigureWithClock(UARTConfig{}, clockHz) |
| py32.RCC.SetICSCR_HSI_FS(py32.RCC_ICSCR_HSI_FS_Freq24MHz) | ||
|
|
||
| ConfigureSystemTimer(24_000_000) | ||
| machine.InitSerial() |
There was a problem hiding this comment.
This perhaps needs to be moved into an init() function?
See #5200 (comment)
| ], | ||
| "build-tags": [ | ||
| "embedfire_py32f002b", | ||
| "default_uart_pins" |
|
I've taken a look and have some reservations on exported API chosen. I'd like to better understand how we define APIs in machine package to understand if the exported functions implemented here match with what the rest of TinyGo does. Basically working on getting something like |
|
I vibe coded a tool to help us understand the tinygo APIs better. I'll invite you all to check it out to understand what our APIs look like in TinyGo #5224 |
soypat
left a comment
There was a problem hiding this comment.
Running tgdoc I've found what look like discrepancies with usual TinyGo API.
You can browse our APIs by running go run ./cmd/tgdoc -http=:18080 ../tinygo/targets ../tinygo/src/machine given tinygodoc shares tinygo parent directory
https://github.com/tinygo-org/tinygodoc
|
|
||
| // ConfigureWithClock initializes the UART using the provided peripheral clock | ||
| // frequency (in Hz). This avoids assuming a fixed MCU clock. | ||
| func (uart *UART) ConfigureWithClock(config UARTConfig, clockHz uint32) error { |
There was a problem hiding this comment.
can we replace clockHz with CPUFrequency() internal call to Configure?
|
|
||
| // InitSerialWithClock configures the default Serial using the supplied | ||
| // peripheral clock frequency. | ||
| func InitSerialWithClock(clockHz uint32) { |
| } | ||
|
|
||
| // Configure pin for use by UART | ||
| func ConfigureUARTPin(pin Pin, af uint8) { |
There was a problem hiding this comment.
Shouldn't this be Pin.Configure followed by an SetAltFunc? (is it really necessary to export this?)
There was a problem hiding this comment.
Or unexport it if it is expected to only be called internally for UART configuration
There was a problem hiding this comment.
You are right, cleaned up to just Pin.Configure+SetAltFunc.
This needs to be exported, because other boards may need explicit call for non-default UART pins.
There was a problem hiding this comment.
Non default pins can be configured with the UARTConfig struct. Please unexport this.
There was a problem hiding this comment.
Unfortunately it can't be configured using UARTConfig on PY32F. We need to pass the AF parameter somehow.
There was a problem hiding this comment.
However, I will try to fix it somehow...
| func SetCPUFrequency(frequency uint32) { | ||
| CPUFrequencyHz = frequency | ||
| } |
There was a problem hiding this comment.
This is a very confusing API- it does not actually change the frequency. Prefer unexporting it and either linking with //go:linkname compiler directive from runtime package or setting as constant at build time.
There was a problem hiding this comment.
I agree - the function is removed. The variable CPUFrequencyHz still needs to be exported to allow clock change from user code.
There was a problem hiding this comment.
Why do you want to allow changing CPU frequency from user code?
There was a problem hiding this comment.
This API does not exist yet in TinyGo, we'd likely need to discuss the best design going forward if we are to add it
There was a problem hiding this comment.
For two simple reasons (correct me if I'm not seeing something):
- TinyGo comes with a finite set of supported dev boards. In practice, final product boards differ, so they may need other setting.
- There may be application which need to set clock dynamically, e.g. to save energy.
There was a problem hiding this comment.
There was a problem hiding this comment.
Yes, I need that for one of my projects. Thanks for being open to discuss it!
There was a problem hiding this comment.
I'd hate for this to stay in a stale state while we discuss the CPUFrequency API. Could you implement CPU frequency API on your side and test things and let us know how your experimentation goes?
There was a problem hiding this comment.
This API is still very unintuitive because it does exactly none of what it says it does. Calling this function does not actually do anything yet documentation and the name state otherwise. Either remove the CPU setting frequency API or figure out how to include it in an intuitive way.
Consider we want new users of the PY32 with TinyGo to have a smooth experience and not be confused as to why the CPU frequency has not changed after calling the function that says it changes CPU frequency. If need be have this function do all the reinitialization of all peripherals to fulfill the objective.
There was a problem hiding this comment.
The function was removed - no SetCPUFrequency anymore.
|
@soypat I'm using this PY32F port already for few firmware projects. No change was needed for a month or two, so the code seems to be stable. What do you think about merging it? |
| func SetCPUFrequency(frequency uint32) { | ||
| CPUFrequencyHz = frequency | ||
| } |
There was a problem hiding this comment.
This API is still very unintuitive because it does exactly none of what it says it does. Calling this function does not actually do anything yet documentation and the name state otherwise. Either remove the CPU setting frequency API or figure out how to include it in an intuitive way.
Consider we want new users of the PY32 with TinyGo to have a smooth experience and not be confused as to why the CPU frequency has not changed after calling the function that says it changes CPU frequency. If need be have this function do all the reinitialization of all peripherals to fulfill the objective.
| } | ||
|
|
||
| // Configure pin for use by UART | ||
| func ConfigureUARTPin(pin Pin, af uint8) { |
There was a problem hiding this comment.
Non default pins can be configured with the UARTConfig struct. Please unexport this.
| for uart.Bus.SR.Get()&py32.USART_SR_TXE == 0 { | ||
| } |
There was a problem hiding this comment.
I missed these hot loops. can we take measures to prevent runaway spinning?
| for uart.Bus.SR.Get()&py32.USART_SR_TC == 0 { | ||
| } |
| func (p Pin) SetAltFunc(af uint8) { | ||
| port, pin := p.getPort() | ||
| if pin >= 8 { | ||
| port.AFRH.ReplaceBits(uint32(af), 0xF, (pin%8)*4) | ||
| } else { | ||
| port.AFRL.ReplaceBits(uint32(af), 0xF, (pin%8)*4) | ||
| } | ||
| } |
There was a problem hiding this comment.
I'm still very confused as to why these APIs are here. I am not familiar with the py32. Maybe documenting them here would clarify to me and future users how to use Altfuncs on the py32 and also maybe give me clarity to suggest alternative APIs?
There was a problem hiding this comment.
This is how you configure a pin for an alternative function, such as UART RX or ADC IN.
There was a problem hiding this comment.
OK, I think we can approach the UART config of pins like RP2040 does it:
type pinFunc: https://github.com/tinygo-org/tinygo/blob/dev/src/machine/machine_rp2_gpio.go#L59setPinFunc: https://github.com/tinygo-org/tinygo/blob/dev/src/machine/machine_rp2_gpio.go#L145-L152- pinfunc definitions for RP2040:
tinygo/src/machine/machine_rp2_2040.go
Lines 99 to 124 in 1a1506e
- uart config of pins: https://github.com/tinygo-org/tinygo/blob/dev/src/machine/machine_rp2_uart.go#L53-L57
what do you think?
There was a problem hiding this comment.
FYI: The exported API is PinMode:
tinygo/src/machine/machine_rp2_2040.go
Lines 36 to 46 in 1a1506e
|
Reviewed the five new commits. The branch was rebased, so I compared Verified as fixedAll four items are done, and two are solved better than I suggested. 1. The misleading HSI constant. Fixed upstream in the SVD, which is the right layer. The submodule moved to 2. 3. 4. Mask used as a value. CI. One new comment
In both SVDs Writing 4 into a field declared as 1 or 2 bits still gives the right result today, but only by accident. The generated setter is You are already patching field metadata upstream. Correcting these two On the file countThe earlier comments 2 and 3 traded file count for correctness and put three files back. The count went from 36 to 33 and then to 35, so the total across both rounds is 36 to 35. That moves the wrong way against the earlier request to reduce build tag variants. The SVD fix above would bring it to 34 and remove the last HSI variant. Still open
Fixing the constants in the SVD instead of working around them in Go is the right way to do it. It removes a whole class of problem. |
|
I have rebased and resolved that makefile refactoring. @soypat , I think the last unresolved point is whether Pin.SetAltFunc should remain exported. My position is to keep it exported, for two reasons: First, STM32 already exposes the same kind of functionality, so this seems consistent with existing practice and may be a candidate for a common API in the future. Second, this is a practical requirement for my current project. The generic HAL is valuable, but it cannot cover every peripheral and pin-mapping combination across all MCU vendors and families. There will always be cases where an application needs a low-level escape hatch to select the alternate function required by a specific peripheral - some families have exotic peripherals like 7-segment LED driver or motor MOSFET drivers. For those reasons, I need SetAltFunc to remain accessible to user code. I am happy to improve its naming or documentation if that would address concerns about how this low-level API should be presented. |
|
This is one of the exports of https://github.com/tinygo-org/tinygo/blob/dev/src/machine/machine_stm32_moder_gpio.go#L165 |
soypat
left a comment
There was a problem hiding this comment.
Looks good. SetAltFunc is fine. We tend to export non-standard low level APIs in the rest of TinyGo so the harm is not too bad. I do have a question regarding the switch/vs table approach: did you try both out?
|
|
||
| // PY32F003 datasheet tables 3-6 through 3-8 and PY32F030 datasheet | ||
| // tables 3-1 through 3-3. | ||
| func uartPinAF(uartNum uint8, pin Pin, tx bool) (uint8, bool) { |
There was a problem hiding this comment.
was this more effective than the table approach?
There was a problem hiding this comment.
Yes, it was. It wins on flash, costs no more RAM, and gives LLVM better opportunities to eliminate irrelevant branches for constant UART/pin configurations.
There was a problem hiding this comment.
Anyway, thanks for approving the SetAltFunc. Don't worry, I'm still thinking about the full support of all peripherals through PinMode as you suggested. However, it's a substantial piece of work, which could be perhaps done as a future feature. It's not only about the code optimization, but also about how to extract all that information reliably from all the datasheets and generate code from it.
|
Thank you very much for all the work on this @burgrp and to @soypat & @b0ch3nski for all the reviews/feedback to help get it in. Now squash/merging! |

Hi, this is a port of of Puya PY32F super-cheap micro-controllers.
There are two new boards and supported (available on Aliexpress) for PY32F030 and PY32002b.
PY32F030 and PY32002b are supported, PY32F002a and PY32F071 will follow.
It follows the STM32 SVD pattern - there's a small TinyGo SVD repository dependent on Rust py32-rs repo. The repository is here: https://github.com/burgrp/py32-svd . I think it would be worth to move it to tinygo-org.